package de.blitzcoder.collide.gui.debugger;

import de.blitzcoder.collide.Config;
import de.blitzcoder.collide.Interface;
import de.blitzcoder.collide.engine.RunningProcess;
import de.blitzcoder.collide.icons.Icon;
import de.blitzcoder.collide.util.Log;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;

/**
 *
 * @author blitzcoder
 */
public class BlitzMaxDebugger extends JPanel implements TreeWillExpandListener {

    // Properties
    private RunningProcess process;

    // Components
    private JList stackTraceList;
    private DefaultListModel stackTraceListModel;
    private JTree tree;
    private DefaultTreeModel treeModel;
    private JToolBar toolBar;
    private JButton stepOutBtn;
    private JButton stepInBtn;
    private JButton stepOverBtn;
    private JButton continueBtn;
    // Data
    private LinkedList<DebugTraceFunction> stackTrace;
    private HashMap<String, DebugVariable> objectAddresses;

    public BlitzMaxDebugger(RunningProcess process) {
        this.process = process;

        stackTrace = new LinkedList<DebugTraceFunction>();

        setupUI();

    }

    /*
     * This method parses an ObjectDump
     * Example:
     * ******************
     * ~>ObjectDump@09fac790{
     * ~>Type TData                         --- Hirachy
     * ~>Type TList                         -^
     * ~>Field _head:TLink=$09fac7a0        Fields
     * ~>}
     * ~>
     * ******************
     */
    public void parseObjectDump(String out) {
        String[] lines = out.split("\n");

        String address = "";
        DebugVariable dumpVar = null;

        for (int i = 0; i < lines.length; i++) {

            String line = lines[i];

            if (line.length() <= 2 || line.equals("~>}")) {
                continue;
            }

            if (line.startsWith("~>ObjectDump@")) {

                address = "$" + line.substring(13).replace("{", "");

                Log.log("Got Address: " + address);

                // Find the Object with this address
                dumpVar = objectAddresses.get(address);


                Object[] obj = dumpVar.getChildren().toArray();
                dumpVar.removeAllChildren();
                int[] arr = new int[obj.length];
                for (int i_=0;i_<arr.length;i_++)
                    arr[i_] = i_;
                treeModel.nodesWereRemoved(dumpVar, arr, obj);

            } else if (line.startsWith("~>Type ")) {

                // Ignore
            } else {

                DebugVariable var;

                /*
                 * String looks like this: "Local gaga:Int=3"
                 */
                if (line.indexOf("=") != -1) {

                    line = line.substring(2); // Cut ~>

                    int pos = line.indexOf("=");

                    String name = line.substring(0, pos);
                    String value = line.substring(pos + 1);// We dont want the =

                    var = new DebugVariable(name, value);

                    if (value.startsWith("$") && !objectAddresses.containsKey(value)) {

                        objectAddresses.put(value, var);

                    }

                    dumpVar.addChild(var);
                    
                    treeModel.nodesWereInserted(dumpVar, new int[] {dumpVar.getChildCount()-1});

                }
            }

        }

    }


    /*
     * This methods parses a stack trace given by the BlitzMax-Debugger
     *
     * Example:
     * ******************
     * ~>StackTrace{                            ** Start
     * ~>@/home/blitzcoder/btbn.bmx<6,2>        ** A new Function -> @/path/to/source/file.bmx<row,col>
     * ~>Function btbn                          ** The functions (or methods) name
     * ~>Local a:String=""                      ** --|
     * ~>Local b:TLIst=$085c3960                **   ----- Variables and their values. The $-Adress can be used to get more detailed information about the object
     * ~>Local c:Int=0                          ** --|
     * ~>@/home/blitzcoder/btbn.bmx<13,2>       **
     * ~>Function foo                           **
     * ~>Local gaga:Int=3                       **
     * ~>Local wusa:String="Hallo"              **
     * ~>}                                      ** End
     * ******************
     */
    public void parseStackTrace(String trace) {

        // Bad Windows, replace carrige returns
        trace = trace.replace("\r", "");

        objectAddresses = new HashMap<String, DebugVariable>();
        stackTrace.clear();

        // Remove StackTrace marks
        trace = trace.replace("~>StackTrace{", "");
        trace = trace.replace("~>}", "");

        DebugTraceFunction currentFunction = null;

        String[] lines = trace.split("\n");
        for (int i = 0; i < lines.length; i++) {

            String line = lines[i];

            if (line.length() <= 2) // 2 is the length of "~>", ignore empty lines
            {
                continue;
            }

            if (line.startsWith("~>@")) {

                // The a new Funktion in StackTrace
                // Parse File and line
                line = line.replace("~>@", "");
                int pos;

                // File
                pos = line.indexOf("<");
                String file = line.substring(0, pos);
                file = file.replace("$BMXPATH", Config.getFileProperty("system.bmk").getParentFile().getParent());
                Log.log("Path: " + file);

                // Position
                line = line.substring(pos);
                line = line.replace("<", "");
                line = line.replace(">", "");
                String[] positions = line.split(",");
                int row = Integer.parseInt(positions[0]);
                int col = Integer.parseInt(positions[1]);

                // The name of the function is given as a new line
                String name = lines[i + 1].substring(2); // Cut the "~>"

                DebugTraceFunction function = new DebugTraceFunction(name, file, row, col);
                stackTrace.addLast(function);

                currentFunction = function;

                // Ignore the next line
                i++;

            } else if (line.startsWith("~>")) {

                // a Debug Variable
                // Parse Name, Type and Value

                if (currentFunction == null) {
                    throw new Error("No current Function...");
                }

                line = line.replace("~>", "");

                DebugVariable var;

                /*
                 * String looks like this: "Local gaga:Int=3"
                 */
                if (line.indexOf("=") != -1) {

                    int pos = line.indexOf("=");

                    String name = line.substring(0, pos);
                    String value = line.substring(pos + 1);// We dont want the =

                    var = new DebugVariable(name, value);

                    // Request an Object Dump
                    if (value.startsWith("$")) {

                        objectAddresses.put(value, var);

                        if (name.toLowerCase().indexOf("options") != -1) {
                            Log.log("line: "+line);
                            Log.log(value);
                        }

                    // Do not fetch data now, its too slow
                    // dumps are reqeusted when the user klicks the treeview
                    /*
                    char[] chars = ("d" + value).toCharArray();
                    int[] ints = new int[chars.length];
                    for (int c = 0; c < ints.length; c++) {
                    ints[c] = chars[c];
                    }
                    process.writeDebugger(ints);
                     */

                    }

                } else {

                    var = new DebugVariable(line, "");

                }
                currentFunction.addVariable(var);

            }

        }

        fillUI();

    }

    private void addButtonActions() {

        stepInBtn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                process.writeDebugger(69);
            }
        });

        stepOverBtn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                process.writeDebugger(83);
            }
        });

        stepOutBtn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                process.writeDebugger(76);
            }
        });

        continueBtn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                process.writeDebugger(82);

                // clear the views
                stackTraceListModel.clear();
                treeModel.setRoot(new DebugVariable("Code", ""));

            }
        });

    }

    private void setupUI() {

        toolBar = new JToolBar();

        stepInBtn = new JButton();
        stepOutBtn = new JButton();
        stepOverBtn = new JButton();
        continueBtn = new JButton();

        stepInBtn.setIcon(Icon.load("stepin.png"));
        stepOutBtn.setIcon(Icon.load("stepout.png"));
        stepOverBtn.setIcon(Icon.load("stepover.png"));
        continueBtn.setIcon(Icon.load("continue.png"));

        stepInBtn.setToolTipText("Step In");
        stepOutBtn.setToolTipText("Step Out");
        stepOverBtn.setToolTipText("Step Over");
        continueBtn.setToolTipText("Continue");

        toolBar.setFloatable(false);
        toolBar.add(stepInBtn);
        toolBar.add(stepOutBtn);
        toolBar.add(stepOverBtn);
        toolBar.add(continueBtn);

        addButtonActions();

        JSplitPane splitPane = new JSplitPane();

        stackTraceListModel = new DefaultListModel();
        stackTraceList = new JList(stackTraceListModel);

        treeModel = new DefaultTreeModel(new DebugVariable("Code", ""));
        tree = new JTree(treeModel);
        tree.addTreeWillExpandListener(this);



        JScrollPane stackTraceScroller = new JScrollPane(stackTraceList);
        JScrollPane treeTableScroller = new JScrollPane(tree);

        treeTableScroller.setBorder(BorderFactory.createTitledBorder("Local Variables"));
        stackTraceScroller.setBorder(BorderFactory.createTitledBorder("Stack Trace"));

        splitPane.setTopComponent(stackTraceScroller);
        splitPane.setBottomComponent(treeTableScroller);

        // Layout
        setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.weightx = 1.0;
        c.weighty = 0.0;
        c.gridx = 0;
        c.gridy = 0;
        c.fill = c.BOTH;
        add(toolBar, c);
        c.weighty = 1.0;
        c.gridy = 1;
        add(splitPane, c);


        stackTraceList.addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent e) {
                fillTree();
                selectFunctionLine();
            }
        });

    }

    private void fillUI() {

        // File StackTraceList
        stackTraceListModel.clear();
        ListIterator<DebugTraceFunction> it = stackTrace.listIterator();
        while (it.hasNext()) {
            stackTraceListModel.addElement(it.next());
        }

        stackTraceList.setSelectedIndex(stackTraceListModel.getSize() - 1);

    }

    private void fillTree() {

        DebugVariable var = new DebugVariable("Code", "");

        if (stackTraceList.getSelectedIndex() == -1) { // No stack trace
            treeModel.setRoot(var);
            return;
        }


        DebugTraceFunction func = BlitzMaxDebugger.this.stackTrace.get(stackTraceList.getSelectedIndex());


        ListIterator<DebugVariable> it = func.getVariables().listIterator();
        while (it.hasNext()) {
            DebugVariable var_ = it.next();
            var.addChild(var_);
        }

        treeModel.setRoot(var);
    }

    private void selectFunctionLine() {
        int pos = stackTraceList.getSelectedIndex();

        if (pos == -1) {
            return;
        }

        final DebugTraceFunction func = stackTrace.get(pos);

        Interface.selectLineInFile(func.getFile(), func.getLine());

    }

    @Override
    public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {

        Object obj = event.getPath().getLastPathComponent();
        if (obj instanceof DebugVariable) {

            DebugVariable var = (DebugVariable) obj;

            // Tell the debugger to give the children,
            // if they have not already been fetched
            if (var.getChildCount() == 0 && !var.isLeaf()) {

                var.addChild(new DebugVariable("Loading..."," "));

                Log.log("Getting address: "+var.getValue());
                char[] chars = ("d" + var.getValue().replace("$", "")).toCharArray();
                int[] ints = new int[chars.length];
                for (int c = 0; c < ints.length; c++) {
                    ints[c] = chars[c];
                }
                process.writeDebugger(ints);

            }

        } else {
            throw new Error("No DebugVariable");
        }

    }

    @Override
    public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
        // Interface TreeWillExpandListener
        // Ignore
    }
}
